OpenFisca-US¶
This is a working prototype of an OpenFisca-powered microsimulation model for the US tax system. Note that this is a very minimal implementation with only a few variables implemented, and figures therefore will not match aggregates.
Interface¶
The general interface is largely inherited from tools developed for OpenFisca-UK.
Populations¶
Population-level analyses can use Microsimulation.
from openfisca_us import Microsimulation
import plotly.express as px
sim = Microsimulation()
years = list(range(2018, 2028))
incomes = [sim.calc("Taxes", year).sum() for year in years]
px.line(x=years, y=incomes).update_layout(template="plotly_white", xaxis_title="Year", yaxis_title="Tax Aggregate", xaxis_tickvals=years)
This class also has in-built tools for marginal tax rate calculation, including handling for MTR calculation on variables of different entities (e.g. MTR of tax unit tax liability with respect to individual variables).
mtr = sim.deriv("Taxes", wrt="earned").groupby(sim.calc("earned")).mean().rolling(100).mean()
px.line(mtr[mtr.index < 1e+6]).update_layout(template="plotly_white", xaxis_title="Earned income", yaxis_title="Effective MTR", showlegend=False)
Individuals¶
Hypothetical tax scenarios are simple to calculate with IndividualSim.
from openfisca_us import IndividualSim
import pandas as pd
single_filer = IndividualSim()
def create_single_blind_tu(*args):
sim = IndividualSim(*args, year=2021)
sim.add_person(name="person")
sim.add_taxunit(head="person", MARS="SINGLE", blind_head=True)
sim.vary("earned")
return sim
single_filer = create_single_blind_tu()
# a useful OpenFisca feature is the equal treatment of input, intermediate and output variables
# any operation can be done on either, e.g. MTR calculation wrt an intermediate variable
# like below
single_filer.vary("earned")
results = pd.DataFrame({
"Earned income": single_filer.calc("earned")[0],
"Taxes": single_filer.calc_deriv("Taxes", wrt="earned"),
"Standard deduction": single_filer.calc_deriv("standard_deduction", wrt="earned")
})
px.line(results, x="Earned income", y=["Taxes", "Standard deduction"]).update_layout(
yaxis_tickformat="%",
template="plotly_white",
xaxis_title="Earned income",
yaxis_title="Derivative"
)
single_filer = create_single_blind_tu()
# a useful OpenFisca feature is the equal treatment of input, intermediate and output variables
# any operation can be done on either, e.g. MTR calculation wrt an intermediate variable
# like below
single_filer.vary("earned")
results = pd.DataFrame({
"Earned income": single_filer.calc("earned")[0],
"Taxes": single_filer.calc("Taxes")[0],
"Standard deduction": single_filer.calc("standard_deduction")[0],
"After-tax income": single_filer.calc("AfterTaxIncome")[0],
})
px.line(results, x="Earned income", y=["Taxes", "Standard deduction", "After-tax income"]).update_layout(
yaxis_tickprefix="$",
template="plotly_white",
xaxis_title="Earned income",
yaxis_title="Amount"
)
Reforms¶
Parametric reforms can now be specified from a single YAML file.
from openfisca_us.reforms import reform_from_file
# a reform that multiplies the basic standard deduction by 10
reform = reform_from_file("tax_cut.yaml")
baseline = Microsimulation()
reformed = Microsimulation(reform)
years = list(range(2018, 2028))
revenues = [reformed.calc("Taxes", year).sum() - baseline.calc("Taxes", year).sum() for year in years]
px.line(x=years, y=revenues).update_layout(template="plotly_white", xaxis_title="Year", yaxis_title="Reform revenue", xaxis_tickvals=years)
One of the most useful features of OpenFisca is its Python interface for reforms, allowing multi-reform analyses to be calculated procedurally in code.
from openfisca_us.reforms import parametric_reform
def increase_standard_deduction(amount):
def modify_params(parameters):
std_ded = parameters.tax.deductions.standard.amount.filer
for MARS_type in std_ded.children:
current_value = std_ded.children[MARS_type].get_at_instant("2020-01-01")
new_value = current_value + amount
std_ded.children[MARS_type].update(period="year:2020:10", value=new_value)
return parameters
return parametric_reform(modify_params)
results = []
# for each level of StD increase:
baseline = create_single_blind_tu()
for amount in range(1000, 200000, 5000):
# generate two hypothetical scenario simulations
reform = create_single_blind_tu(increase_standard_deduction(amount))
# add all the results as rows to a dataframe, with each row labelled with its reform info
results += [pd.DataFrame({
"Earned income": baseline.calc("earned")[0],
"Taxes (Baseline)": baseline.calc("Taxes")[0],
"Taxes (Reform)": reform.calc("Taxes")[0],
"SD increase": amount
})]
results = pd.concat(results)
px.line(results, x="Earned income", y=["Taxes (Baseline)", "Taxes (Reform)"], animation_frame="SD increase").update_layout(
yaxis_tickprefix="$",
template="plotly_white",
xaxis_title="Earned income",
yaxis_title="Amount"
)